#include "slider.h"

#include "math.h"

#include <QLinearGradient>
#include <QRadialGradient>

Slider::Slider( Widget* _parent, Engine* _engine, int _x, int _y, int _w, int _h,
                const QColor& _minColor, const QColor& _maxColor, const QColor& _ballColor )
                    :Widget( _parent, _engine, _x, _y, _w, _h )
{
    pos = 0.5;
    h2 = h/2;
    h4 = h2/2;
    range = w - h;
    ballRadius = h4 + h4/2;

    sliderPixmap = new QPixmap( w + 1, h + 1 );
    sliderPixmap->fill( QColor( 0, 0, 0, 0 ) );

    showWidth = 0;
    showGraph.AddSimpleProperty( "w", showWidth );
    showAnime = new Animation( engine->fps, 400 );
    showGraph.AddInterpolator( showAnime->GetTime(), 0, 400, "w", 0, w );
    showAnime->AddObserver( this );
    engine->AddAnimation( showAnime );

    innerBallRect = new QRect();
    sliderRect = new QRect( 0, h4, w, h2 );

    QLinearGradient sliderGradient( 0, h2, w, h2 );
    sliderGradient.setColorAt( 0, _minColor );
    sliderGradient.setColorAt( 1, _maxColor );
    sliderBrush = new QBrush( sliderGradient );

    QRadialGradient ballGradient( w/2, h2, ballRadius );
    ballGradient.setRadius( ballRadius );
    ballGradient.setColorAt( 0, _ballColor );
    ballGradient.setColorAt( 1, Qt::white );
    ballBrush = new QBrush( ballGradient );

    ballPulse = new Animation( engine->fps, 500 );
    ballGradientRadius = ballRadius;
    animeGraph.AddSimpleProperty( "gradientRadius", ballGradientRadius );
    om_ni = new OneMinus_NI< float >( new Linear_NI< float >(), true );
    animeGraph.AddInterpolator( ballPulse->GetTime(), 0, 500, "gradientRadius", ballRadius/2, ballRadius, om_ni );
    ballPulse->AddObserver( this );
    engine->AddAnimation( ballPulse );

    dragging = false;
    onBall = false;
    mouseTracking = true;

    SetPosition( 0.5 );
}

Slider::~Slider()
{
    delete sliderPixmap;
    delete innerBallRect;
    delete ballBrush;
    delete sliderBrush;
    delete ballPulse;
    delete om_ni;
    delete sliderRect;
}

void Slider::doShow()
{
    paintEnabled = true;
    showAnime->Stop();
    showAnime->Play();
}

void Slider::doHide()
{
    visible = false;
    showAnime->Reverse( true );
    showAnime->Play();
}

void Slider::SetPosition( double newPos )
{
    if( newPos >= 0 && newPos <= 1 )
    {
        pos = newPos;

        for( int i=0; i<listeners.size(); i++ )
        {
            listeners.at(i)->OnPosChanged( this, pos );
        }
    }
}

void Slider::Paint( QPainter &widget_painter )
{
    sliderPixmap->fill( QColor( 0, 0, 0, 0 ) );
    QPainter painter( sliderPixmap );
    painter.setPen( Qt::black );
    painter.setBrush( *sliderBrush );
    painter.drawRoundedRect( 0, h4, w, h2, 3, 3 );

    int bx = h2 + pos * range;
    QPoint ballCenter( bx, h2 );
    QRect outerBallRect( bx - h2, 0, h, h );
    innerBallRect->setRect( bx - ballRadius, h2 - ballRadius, 2 * ballRadius, 2 * ballRadius );

    painter.setPen( Qt::NoPen );
    painter.drawEllipse( ballCenter, h2, h2 );

    painter.setPen( Qt::black );
    painter.drawArc( outerBallRect, 30 * 16, 120 * 16 );
    painter.drawArc( outerBallRect, -30 * 16, -120 * 16 );

    QRadialGradient* gradient = ( QRadialGradient* )ballBrush->gradient();
    gradient->setRadius( ballGradientRadius );
    gradient->setCenter( bx, h2 );
    gradient->setFocalPoint( bx, h2 );
    painter.setBrush( *ballBrush );
    painter.drawEllipse( QPoint( bx, h2 ), ballRadius, ballRadius );
    painter.end();

    if( showWidth > 1 )
    {
        widget_painter.drawPixmap( x, y, *sliderPixmap, 0, 0, showWidth + 1, h + 1 );
    }
}

// mouse handling
void Slider::OnMousePressed( QMouseEvent *e, bool inside, int x_rel, int y_rel )
{
    if( inside )
    {
        if( innerBallRect->contains( x_rel, y_rel, true ) )
        {
            ballPulse->Pause();
            prevX = x_rel;
            prevPos = pos;
            dragging = true;

            for( int i=0; i<listeners.size(); i++ )
            {
                listeners.at(i)->OnStartDrag( this );
            }

        } else
        {
            if( sliderRect->contains( x_rel, y_rel, true ) &&
                x_rel >= h2 && x_rel <= w - h2 )
            {
                pos = ( x_rel - h2 ) / ( float )range;

                for( int i=0; i<listeners.size(); i++ )
                {
                    listeners.at(i)->OnPosChanged( this, pos );
                    listeners.at(i)->OnClickOrOnEndDrag( this, pos );
                }
            }
        }
    }
}

void Slider::OnMouseReleased( QMouseEvent *e, bool inside, int x_rel, int y_rel )
{
    if( dragging )
    {
        dragging = false;
        ballPulse->Play();

        for( int i=0; i<listeners.size(); i++ )
        {
            listeners.at(i)->OnClickOrOnEndDrag( this, pos );
        }
    }
}

void Slider::OnMouseMoved( QMouseEvent *e, bool onWidget, int x_rel, int y_rel )
{
    if( dragging )
    {
        // computes the new pos
        pos = prevPos + ( x_rel - prevX ) / ( float )range;
        if( pos < 0 ) pos = 0;
        if( pos > 1 ) pos = 1;

        for( int i=0; i<listeners.size(); i++ )
        {
            listeners.at(i)->OnPosChanged( this, pos );
        }

    } else
    {
        if( onWidget )
        {
            if( innerBallRect->contains( x_rel, y_rel, true ) )
            {
                onBall = true;
                ballPulse->Play();
            } else
            {
                onBall = false;
            }
        } else
        {
            onBall = false;
        }
    }
}

// listeners handling
void Slider::AddSliderListener( SliderListener* sl )
{
    listeners.append( sl );
}

// AnimationObserver implementation
void Slider::OnPause(Animation* a){}
void Slider::OnStop(Animation* a){}
void Slider::OnPlay(Animation* a){}

void Slider::OnEnd(Animation* a)
{
    if( a == showAnime )
    {
        if( !a->IsReversed() )
        {
            visible = true;
            Showed();
        }
        else
        {
            paintEnabled = false;
            Hidden();
        }
    }
    else
    {   // ball animation
        if( !( a->IsReversed() && !onBall ) )
        {
            a->Reverse();
            a->Play();
        }
    }
}


